import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Starter bot implementation.
 */
public class MyBot extends Bot {

	private static final int SEEN_TIMEOUT = 20;
	private Map<Tile, Tile> orders = new HashMap<Tile, Tile>();
	private int[][] seenTiles;
	private List<Ant> myAnts = new ArrayList<Ant>();
	private final Set<Tile> vision = new HashSet<Tile>();
	private final Set<Tile> offsets = new HashSet<Tile>();

	private Map<Tile, Ant> antsCreatedInThePast = new HashMap<Tile, Ant>();

	private BufferedWriter writer;

	private int turn = 0;

	/**
	 * Main method executed by the game engine for starting the bot.
	 * 
	 * @param args
	 *            command line arguments
	 * 
	 * @throws IOException
	 *             if an I/O error occurs
	 */
	public static void main(String[] args) throws IOException {
		new MyBot().readSystemInput();
	}

	public MyBot() {
		try {
			writer = new BufferedWriter(new FileWriter(new File("log.txt")));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean doMoveDirection(Tile antLoc, Aim direction, Ant ant) {
		Ants ants = getAnts();
		// Track all moves, prevent collisions
		Tile newLoc = ants.getTile(antLoc, direction);
		if (ants.getIlk(newLoc).isUnoccupied() && !orders.containsKey(newLoc)
				&& !ants.getMyHills().contains(newLoc)) {
			ants.issueOrder(antLoc, direction);
			orders.put(newLoc, antLoc);
			ant.setPosition(ants.getTile(ant.getPosition(), direction));
			return true;
		} else {
			return false;
		}
	}

	private boolean doMoveLocation(Tile antLoc, Tile destLoc, Ant ant) {
		Ants ants = getAnts();
		// Track targets to prevent 2 ants to the same location
		List<Aim> directions = ants.getDirections(antLoc, destLoc);
		for (Aim direction : directions) {
			if (doMoveDirection(antLoc, direction, ant)) {
				return true;
			}
		}
		return false;
	}

	private void createSeenTiles() {
		Ants ants = getAnts();

		seenTiles = new int[ants.getRows()][ants.getCols()];

		int radius2 = ants.getViewRadius2();

		Tile center = new Tile(0, 0);
		
		for (int i = (int) -Math.ceil(Math.sqrt(radius2)); i < Math.ceil(Math
				.sqrt(radius2)); i++) {
			for (int j = (int) -Math.ceil(Math.sqrt(radius2)); j < Math
					.ceil(Math.sqrt(radius2)); j++) {
				Tile tile = new Tile(i, j);
				if (ants.getDistance(tile, center) <= radius2) {
					vision.add(tile);
				}
			}
		}

		for (int i = (int) -Math.ceil(Math.sqrt(radius2)) - 1; i < Math
				.ceil(Math.sqrt(radius2)) + 1; i++) {
			for (int j = (int) -Math.ceil(Math.sqrt(radius2)) - 1; j < Math
					.ceil(Math.sqrt(radius2)) + 1; j++) {
				Tile tile = new Tile(i, j);
				if (ants.getDistance(tile, center) > radius2) {
					if (vision.contains(new Tile(tile.getRow() - 1, tile
							.getCol()))
							|| vision.contains(new Tile(tile.getRow() + 1,
									tile.getCol()))
							|| vision.contains(new Tile(tile.getRow(), tile
									.getCol() - 1))
							|| vision.contains(new Tile(tile.getRow(), tile
									.getCol() + 1)))
						offsets.add(tile);
				}
			}
		}

		/*
		 * try { for (int i = (int) -Math.ceil(Math.sqrt(radius2)) - 1; i <
		 * Math.ceil(Math.sqrt(radius2)) + 1; i++) { for (int j = (int)
		 * -Math.ceil(Math.sqrt(radius2)) - 1; j < Math.ceil(Math.sqrt(radius2))
		 * + 1; j++) { if (offsets.contains(new Tile(i,j))) writer.write("x");
		 * else if (innerTiles.contains(new Tile(i,j))) writer.write("i"); else
		 * writer.write("o"); } writer.write("\n"); } } catch (IOException e) {
		 * e.printStackTrace(); }
		 */
	}

	private void updateSeenTiles() {
		Ants ants = getAnts();

		for (int i = 0; i < ants.getRows(); ++i) {
			for (int j = 0; j < ants.getCols(); ++j) {
				if (ants.getVisible()[i][j])
					seenTiles[i][j] = turn;
			}
		}
	}

	private void updateAnts() {
		Ants worldState = getAnts();

		Iterator<Ant> iterator = myAnts.iterator();
		while (iterator.hasNext()) {
			Ant myAnt = iterator.next();
			if (worldState.getIlk(myAnt.getPosition()) == Ilk.DEAD)
				iterator.remove();
		}

		for (Tile myHill : worldState.getMyHills()) {
			Ant antCreatedInThePast = antsCreatedInThePast.get(myHill);
			if (antCreatedInThePast != null) {
				if (antCreatedInThePast.getPosition().equals(myHill))
					continue;
				else
					antsCreatedInThePast.remove(myHill);
			}

			if (worldState.getMyAnts().contains(myHill)) {
				Ant createdAnt = new Ant(myHill, worldState);
				myAnts.add(createdAnt);
				antsCreatedInThePast.put(myHill, createdAnt);
			}
		}

		Collections.sort(myAnts, new Comparator<Ant>() {
			@Override
			public int compare(Ant o1, Ant o2) {
				return o1.getPosition().compareTo(o2.getPosition());
			}
		});

		for (Ant myAnt : myAnts)
			myAnt.update(worldState);

		try {
			writer.write("Turn: " + turn + "\n\n");

			for (Ant myAnt : myAnts)
				writer.write(myAnt.getPosition().toString() + "; "
						+ myAnt.getState().toString() + "\n");

			writer.write("\n\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void sendAntsToGatherFood() {
		Ants worldState = getAnts();

		Set<Tile> foods = new HashSet<Tile>(worldState.getFoodTiles());
		
		for (Ant myAnt : myAnts)
			if (myAnt.getState() == AntState.GATHERING_FOOD)
				foods.remove(myAnt.getTarget());
		
		for (Iterator<Tile> iterator = foods.iterator(); iterator.hasNext(); ) {
			Tile food = iterator.next();
			int minDistance = Integer.MAX_VALUE;
			Ant nearest = null;
			for (Tile offset : vision) {
				Tile tile = worldState.getTile(food, offset);
				Ant myAnt = worldState.getMyAntsPositions()[tile.getRow()][tile.getCol()];
				if (myAnt != null) {
					if (myAnt.getState() != AntState.GATHERING_FOOD) {
						int distance = worldState.getDistance(food, myAnt.getPosition());
						if (distance < minDistance) {
							minDistance = distance;
							nearest = myAnt;
						}
					}
				}
			}
			if (nearest != null) {
				nearest.setState(AntState.GATHERING_FOOD);
				nearest.setTarget(food);
				iterator.remove();
			}
		}
	}

	private void sendAntsToRaze() {
		Ants worldState = getAnts();

		Set<Tile> hills = worldState.getEnemyHills();

		List<AntRoute> hillRoutes = new ArrayList<AntRoute>();

		for (Ant myAnt : myAnts) {
			if (myAnt.getState() == AntState.GATHERING_FOOD
					|| myAnt.getState() == AntState.RAZING)
				continue;

			for (Tile hill : hills) {
				int distance = worldState
						.getDistance(myAnt.getPosition(), hill);
				AntRoute route = new AntRoute(myAnt, hill, distance);
				hillRoutes.add(route);
			}
		}

		Collections.sort(hillRoutes);

		for (AntRoute route : hillRoutes) {
			Ant myAnt = route.getStart();
			if (myAnt.getState() == AntState.RAZING)
				continue;

			myAnt.setTarget(route.getEnd());
			myAnt.setState(AntState.RAZING);
		}
	}

	private void sendAntsToExplore() {
		Ants worldState = getAnts();

		for (Ant myAnt : myAnts) {
			if (myAnt.getState() == AntState.GATHERING_FOOD
					|| myAnt.getState() == AntState.RAZING
					|| myAnt.getState() == AntState.EXPLORING)
				continue;

			for (Tile offset : offsets) {
				Tile tile = worldState.getTile(myAnt.getPosition(), offset);

				if (worldState.getIlk(tile).isPassable()
						&& turn - seenTiles[tile.getRow()][tile.getCol()] > SEEN_TIMEOUT) {
					myAnt.setState(AntState.EXPLORING);
					myAnt.setTarget(tile);
				}
			}
		}
	}

	private void sendAntsToFollow() {
		Ants worldState = getAnts();

		for (Ant myAnt : myAnts) {
			if (myAnt.getState() == AntState.IDLE) {
				int minDistance = Integer.MAX_VALUE;
				Ant nearest = null;
				
				for (Ant otherAnt : myAnts) {
					if (otherAnt == myAnt)
						continue;
					
					if (otherAnt.getState() != AntState.IDLE) {
						int distance = worldState.getDistance(myAnt.getPosition(), otherAnt.getPosition());
						if (distance < minDistance) {
							minDistance = distance;
							nearest = otherAnt;
						}
					}
				}
				
				if (nearest != null) {
					myAnt.mimic(nearest);
				}
			}
		}
	}

	private void makeOrders() {
		for (Ant myAnt : myAnts) {
			if (myAnt.getState() != AntState.IDLE) {
				if (myAnt.getNextPosition() != null)
					doMoveLocation(myAnt.getPosition(),
							myAnt.getNextPosition(), myAnt);
			}
		}
	}

	@Override
	public void doTurn() {
		++turn;

		orders.clear();

		Ants ants = getAnts();

		if (seenTiles == null)
			createSeenTiles();

		long ti = System.currentTimeMillis();
		updateSeenTiles();
		long t = System.currentTimeMillis() - ti;

		try {
			writer.write("updateSeenTiles: " + t + "\n");
		} catch (IOException e) {
			e.printStackTrace();
		}

		ti = System.currentTimeMillis();
		updateAnts();
		t = System.currentTimeMillis() - ti;

		try {
			writer.write("updateAnts: " + t + "\n");
		} catch (IOException e) {
			e.printStackTrace();
		}

		ti = System.currentTimeMillis();
		sendAntsToGatherFood();
		t = System.currentTimeMillis() - ti;

		try {
			writer.write("sendAntsToGatherFood: " + t + "\n");
		} catch (IOException e) {
			e.printStackTrace();
		}

		ti = System.currentTimeMillis();
		sendAntsToRaze();
		t = System.currentTimeMillis() - ti;

		try {
			writer.write("sendAntsToRaze: " + t + "\n");
		} catch (IOException e) {
			e.printStackTrace();
		}

		ti = System.currentTimeMillis();
		sendAntsToExplore();
		t = System.currentTimeMillis() - ti;

		try {
			writer.write("sendAntsToExplore: " + t + "\n");
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		sendAntsToFollow();

		makeOrders();

		for (Tile myHill : ants.getMyHills()) {
			for (Ant myAnt : myAnts) {
				if (myAnt.getPosition().equals(myHill)
						&& !orders.containsValue(myHill)) {
					for (Aim direction : Aim.values()) {
						if (doMoveDirection(myHill, direction, myAnt))
							break;
					}
				}
			}
		}
	}
}
